/**
 * 
 */
package io.gitee.falllee.dao.core;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
import org.springframework.util.ObjectUtils;

import io.gitee.falllee.common.util.LogUtils;
import io.gitee.falllee.dao.cache.DataCache;
import io.gitee.falllee.dao.core.table.TableBuilder;
import io.gitee.falllee.dao.model.BaseDaoConfig;
import io.gitee.falllee.dao.model.DaoConst;
import io.gitee.falllee.dao.model.DaoException;
import io.gitee.falllee.dao.persistence.DataMapper;
import io.gitee.falllee.dao.util.CommonUtils;

/**
 * @author batty
 * @date 2022-06-27
 *
 */
public class Dao {
    
    private DataMapper dataMapper;

    private DataCache dataCache;
    
    public Dao(DataMapper dataMapper, DataCache dataCache) {
        this.dataMapper = dataMapper;
        this.dataCache = dataCache;
    }
    
    synchronized BaseDaoConfig getDaoConfig() {
        return CommonUtils.getDaoConfig();
    }
    
    DataMapper getDataMapper() {
        return this.dataMapper;
    }
    
    DataCache getDataCache() {
        return this.dataCache;
    }
    
    public TableBuilder table(String tableName) {
        return new TableBuilder(dataMapper, dataCache, getDaoConfig(), tableName);
    }
    
    public <T> SelectBuilder<T> selectBuilder(Class<T> clazz) {
        return new SelectBuilder<>(this, clazz);
    }
    
    public <T> InsertBuilder<T> insertBuilder(Class<T> clazz) {
        return new InsertBuilder<>(this, clazz);
    }
    
    public <T> UpdateBuilder<T> updateBuilder(Class<T> clazz) {
        return new UpdateBuilder<>(this, clazz);
    }
    
    public <T> DeleteBuilder<T> deleteBuilder(Class<T> clazz) {
        return new DeleteBuilder<>(this, clazz);
    }
    
    public <T> RestoreBuilder<T> restoreBuilder(Class<T> clazz) {
        return new RestoreBuilder<>(this, clazz);
    }
    
    public <T> CacheBuilder<T> cacheBuilder(Class<T> clazz) {
        return new CacheBuilder<>(this, clazz);
    }
    
    public <T> MessageBuilder<T> messageBuilder(Class<T> clazz) {
        return new MessageBuilder<>(this, clazz);
    }
    
    public <T> ExtendBuilder<T> extend(Class<T> clazz) {
        return new ExtendBuilder<>(this, clazz);
    }

    /**
     * 执行sql
     * 
     * @param sql sql语句
     * @param paramMap 参数化值
     * @return
     */
    public List<Map<String, Object>> executeSql(String sql, Map<String, Object> paramMap) {
        Map<String, Object> map = new HashMap<>(16);
        if (!ObjectUtils.isEmpty(paramMap)) {
            if (paramMap.containsKey(DaoConst.SQL)) {
                throw new DaoException(
                        String.format("paramMap cannot contains key:[%s]", DaoConst.SQL));
            }
            Iterator<Entry<String, Object>> iterator = paramMap.entrySet().iterator();
            while (iterator.hasNext()) {
                Entry<String, Object> entry = iterator.next();
                map.put(entry.getKey(), entry.getValue());
            }
        }
        String lowerSql = sql.trim().toLowerCase();
        if (lowerSql.startsWith("select") || lowerSql.startsWith("show") || lowerSql.startsWith("desc")
            || lowerSql.startsWith("explain") || lowerSql.startsWith("call")) {
            if (sql.trim().endsWith(";")) {
                sql = sql.trim().substring(0, sql.trim().length() - 1);
            }
            map.put(DaoConst.SQL, sql);
            if (!map.containsKey(DaoConst.DB_TYPE)) {
                map.put(DaoConst.DB_TYPE, getDaoConfig().getDbType());
            }
            if (!lowerSql.contains("limit ") && !lowerSql.contains("offset ") && !lowerSql.contains("rownum ")
                    && !lowerSql.startsWith("call") && !lowerSql.startsWith("select count(")
                    && !lowerSql.startsWith("select sum(") && !lowerSql.startsWith("select min(")
                    && !lowerSql.startsWith("select max(") && !lowerSql.startsWith("select avg(")) {
                Long start = 0L;
                map.putIfAbsent(DaoConst.START, start);
                map.putIfAbsent(DaoConst.ROWS, DaoConst.SELECT_MAX_SIZE);
                map.putIfAbsent(DaoConst.END, DaoConst.SELECT_MAX_SIZE);
            }
            return dataMapper.select(map);
        } else {
            map.put(DaoConst.SQL, sql);
            Integer res = dataMapper.executeSql(map);
            List<Map<String, Object>> list = new ArrayList<>();
            Map<String, Object> resultMap = new LinkedHashMap<>(3);
            resultMap.put(DaoConst.RESULT, res);
            list.add(resultMap);
            return list;
        }
    }
    
    public void generateEntity() {
        try {
            URL url = Thread.currentThread().getContextClassLoader().getResource("dao/generatorConfig.xml");
            File configFile = new File(url.getFile());
            if (!configFile.exists()) {
                LogUtils.logWarn("file resources/dao/generatorConfig.xml is not exist");
                return;
            }
            List<String> warnings = new ArrayList<>();
            boolean overwrite = true;
            // 直接获取generatorConfig.xml的文件路径 根据具体情况查看
            ConfigurationParser cp = new ConfigurationParser(warnings);
            Configuration config = cp.parseConfiguration(configFile);
            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
            MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
            myBatisGenerator.generate(null);
        } catch (Exception e) {
            LogUtils.logError(String.format("generate entity failed:%s", e.getMessage()));
        }
    }

    /**
     * 从DB获取主键值
     * 
     * @param map
     * @param keyField
     * @param start
     * @param rows
     * @param end
     * @return
     */
    List<Object> getKeyList(Map<String, Object> map, String keyField, Long start, Long rows, Long end) {
        List<Object> keyList = new ArrayList<>();
        map.put(DaoConst.FIELD, keyField);
        map.put(DaoConst.START, start);
        map.put(DaoConst.ROWS, rows);
        map.put(DaoConst.END, end);
        List<Map<String, Object>> keyMapList = dataMapper.select(map);
        for (Map<String, Object> keyMap : keyMapList) {
            Object obj = keyMap.get(keyField);
            if (obj != null) {
                keyList.add(obj);
            }
        }
        return keyList;
    }
}
